/**
 * 用户认证路由模块
 * 
 * 主要功能：
 * 1. 用户注册
 * 2. 用户登录
 * 3. PIN码管理（设置、验证、状态查询）
 * 
 * 安全设计：
 * - 密码使用bcrypt加密（加盐哈希）
 * - PIN码独立加密
 * - PIN码连续错误3次锁定30分钟
 * - 使用JWT Token进行身份认证
 * 
 * @author 个人预算管理系统开发团队
 * @date 2025-10-26
 */

// ==================== 依赖导入 ====================
const express = require('express');                    // Express框架
const router = express.Router();                       // 创建路由实例
const bcrypt = require('bcryptjs');                    // 密码加密库
const { generateToken, authenticateToken } = require('../middleware/auth'); // JWT认证中间件
const db = require('../config/database');              // 数据库连接池

// ==================== 用户注册接口 ====================

/**
 * 用户注册
 * 
 * @route POST /api/auth/register
 * @access Public - 公开接口，无需认证
 * 
 * @description 注册新用户账号，自动创建默认账本
 * 
 * @param {object} req.body - 请求体
 * @param {string} req.body.username - 用户名（必填，唯一）
 * @param {string} req.body.password - 密码（必填，6-20位）
 * @param {string} req.body.nickname - 昵称（选填，默认使用用户名）
 * 
 * @returns {object} 201 - 注册成功响应
 * @returns {number} 201.code - 状态码 201
 * @returns {string} 201.message - 成功消息
 * @returns {object} 201.data - 用户数据
 * @returns {number} 201.data.user_id - 新用户ID
 * @returns {string} 201.data.username - 用户名
 * @returns {string} 201.data.nickname - 昵称
 * @returns {string} 201.data.token - JWT Token
 * 
 * @returns {object} 400 - 参数错误
 * @returns {object} 409 - 用户名已存在
 * @returns {object} 500 - 服务器错误
 * 
 * @example
 * // 请求示例
 * POST /api/auth/register
 * {
 *   "username": "zhangsan",
 *   "password": "123456",
 *   "nickname": "张三"
 * }
 * 
 * // 响应示例
 * {
 *   "code": 201,
 *   "message": "注册成功",
 *   "data": {
 *     "user_id": 1,
 *     "username": "zhangsan",
 *     "nickname": "张三",
 *     "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
 *   }
 * }
 * 
 * 设计思路：
 * 1. 简化注册流程，只需用户名和密码
 * 2. 自动生成邮箱（用户名@budget.local），避免邮箱验证复杂性
 * 3. 注册成功后自动创建默认账本，提升用户体验
 * 4. 立即返回Token，实现注册后自动登录
 * 5. 使用事务保证用户和账本创建的原子性（后续可优化）
 */
router.post('/register', async (req, res) => {
    try {
        // 1. 从请求体获取用户输入
        const { username, password, nickname } = req.body;
        
        // 2. 验证必填项
        // 用户名和密码是注册的最小必要信息
        if (!username || !password) {
            return res.status(400).json({ 
                code: 400, 
                message: '请填写用户名和密码' 
            });
        }
        
        // 3. 自动生成邮箱
        // 设计考虑：简化注册流程，避免邮箱验证的复杂性
        // 格式：用户名@budget.local（本地虚拟邮箱）
        const email = `${username}@budget.local`;
        
        // 4. 检查用户名是否已存在
        // 使用参数化查询防止SQL注入
        // 只查询user_id字段，提高查询效率
        const [existing] = await db.query(
            'SELECT user_id FROM users WHERE username = ?', 
            [username]
        );
        
        // 如果查询结果不为空，说明用户名已被注册
        if (existing.length > 0) {
            return res.status(409).json({ 
                code: 409, 
                message: '用户名已存在' 
            });
        }
        
        // 5. 密码加密
        // 使用bcrypt加密算法（加盐哈希）
        // 第二个参数10是加密强度（salt rounds）
        // 强度越高越安全，但加密时间越长
        // 10是推荐值，在安全性和性能之间取得平衡
        const passwordHash = await bcrypt.hash(password, 10);
        
        // 6. 插入用户记录到数据库
        // 昵称默认使用用户名（nickname || username）
        const [result] = await db.query(
            'INSERT INTO users (username, email, password_hash, nickname) VALUES (?, ?, ?, ?)',
            [username, email, passwordHash, nickname || username]
        );
        
        // 获取新插入用户的自增ID
        const userId = result.insertId;
        
        // 7. 自动创建默认账本
        // 设计考虑：提升用户体验，注册后即可使用
        // 账本名称：我的账本
        // 类型：个人账本（personal）
        // 图标：💰（金币）
        await db.query(
            'INSERT INTO account_books (owner_user_id, book_name, book_type, icon) VALUES (?, ?, ?, ?)',
            [userId, '我的账本', 'personal', '💰']
        );
        
        // 8. 生成JWT Token
        // 用于后续请求的身份认证
        // 包含用户的基本信息（user_id、username、email）
        const token = generateToken({ 
            user_id: userId, 
            username, 
            email 
        });
        
        // 9. 返回注册成功响应
        // 状态码201表示"Created"（资源已创建）
        // 返回Token实现注册后自动登录
        res.status(201).json({
            code: 201,
            message: '注册成功',
            data: {
                user_id: userId,                   // 用户ID
                username,                          // 用户名
                nickname: nickname || username,    // 昵称
                token                              // JWT Token
            }
        });
        
    } catch (error) {
        // 10. 错误处理
        // 记录详细错误到控制台，便于调试
        console.error('注册错误:', error);
        
        // 返回500服务器错误
        // 包含错误消息（开发环境有助于调试）
        res.status(500).json({ 
            code: 500, 
            message: '注册失败', 
            error: error.message 
        });
    }
});

// ==================== 用户登录接口 ====================

/**
 * 用户登录
 * 
 * @route POST /api/auth/login
 * @access Public - 公开接口，无需认证
 * 
 * @description 用户登录验证，返回JWT Token
 * 
 * @param {object} req.body - 请求体
 * @param {string} req.body.username - 用户名（必填）
 * @param {string} req.body.password - 密码（必填）
 * 
 * @returns {object} 200 - 登录成功响应
 * @returns {number} 200.code - 状态码 200
 * @returns {string} 200.message - 成功消息
 * @returns {object} 200.data - 用户数据
 * @returns {number} 200.data.user_id - 用户ID
 * @returns {string} 200.data.username - 用户名
 * @returns {string} 200.data.email - 邮箱
 * @returns {string} 200.data.nickname - 昵称
 * @returns {boolean} 200.data.has_pin - 是否设置了PIN码
 * @returns {string} 200.data.token - JWT Token
 * 
 * @returns {object} 400 - 参数错误
 * @returns {object} 401 - 用户名或密码错误
 * @returns {object} 403 - 账户已锁定
 * @returns {object} 500 - 服务器错误
 * 
 * @example
 * // 请求示例
 * POST /api/auth/login
 * {
 *   "username": "zhangsan",
 *   "password": "123456"
 * }
 * 
 * // 响应示例
 * {
 *   "code": 200,
 *   "message": "登录成功",
 *   "data": {
 *     "user_id": 1,
 *     "username": "zhangsan",
 *     "email": "zhangsan@budget.local",
 *     "nickname": "张三",
 *     "has_pin": true,
 *     "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
 *   }
 * }
 * 
 * 安全设计：
 * 1. 密码验证失败时，不透露是用户名错误还是密码错误（防止用户枚举）
 * 2. 使用bcrypt.compare()验证密码，安全比对哈希值
 * 3. 检查账户状态，防止已锁定账户登录
 * 4. 更新最后登录时间，便于用户管理
 */
router.post('/login', async (req, res) => {
    try {
        // 1. 从请求体获取登录信息
        const { username, password } = req.body;
        
        // 2. 验证必填项
        if (!username || !password) {
            return res.status(400).json({ 
                code: 400, 
                message: '请输入用户名和密码' 
            });
        }
        
        // 3. 查询用户信息
        // 通过用户名查询（不再支持邮箱登录，简化逻辑）
        // 查询所有字段，后续需要验证密码和检查状态
        const [users] = await db.query(
            'SELECT * FROM users WHERE username = ?',
            [username]
        );
        
        // 4. 检查用户是否存在
        // 安全考虑：统一提示"用户名或密码错误"，不透露具体是哪个错误
        // 防止攻击者通过错误消息枚举系统中的用户名
        if (users.length === 0) {
            return res.status(401).json({ 
                code: 401, 
                message: '用户名或密码错误' 
            });
        }
        
        // 获取用户对象（数组第一个元素）
        const user = users[0];
        
        // 5. 检查账户状态
        // 如果账户被锁定（status='locked'），拒绝登录
        // 这是管理员手动锁定的账户，不同于PIN码锁定
        if (user.status === 'locked') {
            return res.status(403).json({ 
                code: 403, 
                message: '账户已被锁定，请联系管理员' 
            });
        }
        
        // 6. 验证密码
        // 使用bcrypt.compare()安全地比对密码
        // 参数1：明文密码（用户输入）
        // 参数2：密码哈希值（数据库存储）
        // 返回true表示密码正确，false表示密码错误
        const isValid = await bcrypt.compare(password, user.password_hash);
        
        // 密码验证失败
        if (!isValid) {
            return res.status(401).json({ 
                code: 401, 
                message: '用户名或密码错误' 
            });
        }
        
        // 7. 更新最后登录时间
        // 使用NOW()函数获取当前数据库服务器时间
        // 这个操作是非关键的，即使失败也不影响登录
        await db.query(
            'UPDATE users SET updated_at = NOW() WHERE user_id = ?', 
            [user.user_id]
        );
        
        // 8. 生成JWT Token
        // 包含用户的完整信息
        const token = generateToken(user);
        
        // 9. 返回登录成功响应
        res.json({
            code: 200,
            message: '登录成功',
            data: {
                user_id: user.user_id,        // 用户ID
                username: user.username,      // 用户名
                email: user.email,            // 邮箱
                nickname: user.nickname,      // 昵称
                has_pin: !!user.pin_hash,     // 是否设置了PIN码（!!转换为布尔值）
                token                         // JWT Token
            }
        });
        
    } catch (error) {
        // 10. 错误处理
        console.error('登录错误:', error);
        res.status(500).json({ 
            code: 500, 
            message: '登录失败', 
            error: error.message 
        });
    }
});

// ==================== PIN码管理接口 ====================

/**
 * 设置PIN码
 * 
 * @route POST /api/auth/pin/set
 * @access Private - 需要JWT Token认证
 * 
 * @description 设置或修改用户的PIN码，用于敏感操作的二次验证
 * 
 * @param {object} req.body - 请求体
 * @param {string} req.body.pin - PIN码（必填，4-6位数字）
 * @param {object} req.user - JWT Token解析出的用户信息
 * @param {number} req.user.user_id - 用户ID
 * 
 * @returns {object} 200 - 设置成功响应
 * @returns {number} 200.code - 状态码 200
 * @returns {string} 200.message - 成功消息
 * 
 * @returns {object} 400 - PIN格式错误
 * @returns {object} 401 - 未认证
 * @returns {object} 500 - 服务器错误
 * 
 * @example
 * // 请求示例
 * POST /api/auth/pin/set
 * Authorization: Bearer <token>
 * {
 *   "pin": "1234"
 * }
 * 
 * // 响应示例
 * {
 *   "code": 200,
 *   "message": "PIN码设置成功"
 * }
 * 
 * 设计思路：
 * 1. PIN码独立于登录密码，提供额外的安全层
 * 2. PIN码用于敏感操作（删除账本、数据导出等）的二次验证
 * 3. PIN码也使用bcrypt加密，安全存储
 * 4. 设置新PIN时重置错误次数和锁定状态
 */
router.post('/pin/set', authenticateToken, async (req, res) => {
    try {
        // 1. 从请求体获取PIN码
        const { pin } = req.body;
        
        // 2. 从JWT Token获取用户ID
        // authenticateToken中间件已将用户信息附加到req.user
        const userId = req.user.user_id;
        
        // 3. 验证PIN格式
        // 使用正则表达式：^\d{4,6}$
        // ^ : 字符串开始
        // \d : 数字字符 [0-9]
        // {4,6} : 重复4到6次
        // $ : 字符串结束
        // 结果：只允许4-6位纯数字
        if (!/^\d{4,6}$/.test(pin)) {
            return res.status(400).json({ 
                code: 400, 
                message: 'PIN码必须是4-6位数字' 
            });
        }
        
        // 4. 加密PIN码
        // 使用与密码相同的bcrypt算法
        // 即使PIN码泄露，攻击者也无法反推原始PIN
        const pinHash = await bcrypt.hash(pin, 10);
        
        // 5. 保存PIN码到数据库
        // 同时重置错误尝试次数和锁定状态
        // 设计考虑：设置新PIN相当于"忘记PIN码"后的重置
        await db.query(
            'UPDATE users SET pin_hash = ?, pin_attempts = 0, pin_locked_until = NULL WHERE user_id = ?',
            [pinHash, userId]
        );
        
        // 6. 返回成功响应
        res.json({
            code: 200,
            message: 'PIN码设置成功'
        });
        
    } catch (error) {
        // 7. 错误处理
        console.error('设置PIN错误:', error);
        res.status(500).json({ 
            code: 500, 
            message: '设置PIN失败', 
            error: error.message 
        });
    }
});

/**
 * 验证PIN码
 * 
 * @route POST /api/auth/pin/verify
 * @access Private - 需要JWT Token认证
 * 
 * @description 验证用户的PIN码，用于敏感操作的二次确认
 * 
 * @param {object} req.body - 请求体
 * @param {string} req.body.pin - PIN码（必填，4-6位数字）
 * @param {object} req.user - JWT Token解析出的用户信息
 * @param {number} req.user.user_id - 用户ID
 * 
 * @returns {object} 200 - 验证成功响应
 * @returns {number} 200.code - 状态码 200
 * @returns {string} 200.message - 成功消息
 * 
 * @returns {object} 400 - 未设置PIN码
 * @returns {object} 401 - PIN码错误（包含剩余机会）
 * @returns {object} 403 - PIN已锁定（包含剩余时间）
 * @returns {object} 404 - 用户不存在
 * @returns {object} 500 - 服务器错误
 * 
 * @example
 * // 请求示例
 * POST /api/auth/pin/verify
 * Authorization: Bearer <token>
 * {
 *   "pin": "1234"
 * }
 * 
 * // 成功响应示例
 * {
 *   "code": 200,
 *   "message": "PIN验证成功"
 * }
 * 
 * // 错误响应示例（还有2次机会）
 * {
 *   "code": 401,
 *   "message": "PIN码错误，还剩2次机会"
 * }
 * 
 * // 锁定响应示例
 * {
 *   "code": 403,
 *   "message": "PIN已锁定，请25分钟后再试"
 * }
 * 
 * 安全机制（防暴力破解）：
 * 1. 错误3次自动锁定30分钟
 * 2. 锁定期间无法尝试，即使输入正确PIN也不接受
 * 3. 锁定时间到期后自动解锁
 * 4. 验证成功后重置错误次数
 * 5. 每次错误都提示剩余机会，但不暴露具体PIN值
 * 
 * 设计思路：
 * 1. 使用数据库字段pin_attempts记录错误次数
 * 2. 使用pin_locked_until记录锁定到期时间
 * 3. 时间比较使用毫秒级精度，避免时区问题
 * 4. 锁定到期后自动重置，无需手动解锁
 */
router.post('/pin/verify', authenticateToken, async (req, res) => {
    try {
        // 1. 从请求体获取PIN码
        const { pin } = req.body;
        
        // 2. 从JWT Token获取用户ID
        const userId = req.user.user_id;
        
        // 3. 查询用户的PIN相关信息
        // 只查询必要字段：pin_hash（用于验证）、pin_attempts（错误次数）、pin_locked_until（锁定到期时间）
        const [users] = await db.query(
            'SELECT pin_hash, pin_attempts, pin_locked_until FROM users WHERE user_id = ?',
            [userId]
        );
        
        // 4. 检查用户是否存在
        // 理论上不会出现（JWT已验证身份），但为安全起见仍需检查
        if (users.length === 0) {
            return res.status(404).json({ 
                code: 404, 
                message: '用户不存在' 
            });
        }
        
        const user = users[0];
        
        // 5. 检查是否已设置PIN码
        // pin_hash为NULL表示用户还没设置PIN
        if (!user.pin_hash) {
            return res.status(400).json({ 
                code: 400, 
                message: '未设置PIN码' 
            });
        }
        
        // 6. 检查是否处于锁定状态
        if (user.pin_locked_until) {
            // 将数据库的时间字符串转为Date对象
            const lockedUntil = new Date(user.pin_locked_until);
            const now = new Date();
            
            // 6.1 如果当前时间还未到锁定到期时间，拒绝验证
            if (now < lockedUntil) {
                // 计算剩余锁定时间（分钟数，向上取整）
                // (lockedUntil - now) 得到毫秒差
                // 除以60000 转换为分钟
                // Math.ceil 向上取整（1.1分钟显示为2分钟）
                const remainingMinutes = Math.ceil((lockedUntil - now) / 60000);
                
                return res.status(403).json({
                    code: 403,
                    message: `PIN已锁定，请${remainingMinutes}分钟后再试`
                });
            } else {
                // 6.2 锁定已过期，自动解锁
                // 重置错误次数为0，清除锁定时间
                // 设计考虑：自动解锁，用户无需手动操作
                await db.query(
                    'UPDATE users SET pin_attempts = 0, pin_locked_until = NULL WHERE user_id = ?',
                    [userId]
                );
            }
        }
        
        // 7. 验证PIN码
        // 使用bcrypt.compare()安全比对
        const isValid = await bcrypt.compare(pin, user.pin_hash);
        
        // 8. PIN码验证失败的处理
        if (!isValid) {
            // 8.1 增加错误尝试次数
            const newAttempts = user.pin_attempts + 1;
            
            // 8.2 检查是否达到锁定阈值（3次）
            if (newAttempts >= 3) {
                // 8.2.1 达到3次，触发锁定机制
                // 创建锁定到期时间：当前时间 + 30分钟
                const lockedUntil = new Date();
                lockedUntil.setMinutes(lockedUntil.getMinutes() + 30);
                
                // 更新数据库：记录错误次数和锁定到期时间
                await db.query(
                    'UPDATE users SET pin_attempts = ?, pin_locked_until = ? WHERE user_id = ?',
                    [newAttempts, lockedUntil, userId]
                );
                
                // 返回403禁止访问状态
                return res.status(403).json({
                    code: 403,
                    message: 'PIN错误次数过多，已锁定30分钟'
                });
            } else {
                // 8.2.2 未达到3次，仅增加错误次数
                await db.query(
                    'UPDATE users SET pin_attempts = ? WHERE user_id = ?',
                    [newAttempts, userId]
                );
                
                // 计算剩余尝试机会：3 - 当前错误次数
                const remainingAttempts = 3 - newAttempts;
                
                // 返回401未授权状态，提示剩余机会
                // 设计考虑：让用户知道还有几次机会，避免被锁定
                return res.status(401).json({
                    code: 401,
                    message: `PIN码错误，还剩${remainingAttempts}次机会`
                });
            }
        }
        
        // 9. PIN验证成功
        // 重置错误尝试次数为0
        // 设计考虑：验证成功后清空之前的错误记录，下次重新计数
        await db.query(
            'UPDATE users SET pin_attempts = 0 WHERE user_id = ?',
            [userId]
        );
        
        // 10. 返回成功响应
        res.json({
            code: 200,
            message: 'PIN验证成功'
        });
        
    } catch (error) {
        // 11. 错误处理
        console.error('验证PIN错误:', error);
        res.status(500).json({ 
            code: 500, 
            message: 'PIN验证失败', 
            error: error.message 
        });
    }
});

/**
 * 查询PIN状态
 * 
 * @route GET /api/auth/pin/status
 * @access Private - 需要JWT Token认证
 * 
 * @description 查询用户的PIN码状态，包括是否设置、是否锁定等信息
 * 
 * @param {object} req.user - JWT Token解析出的用户信息
 * @param {number} req.user.user_id - 用户ID
 * 
 * @returns {object} 200 - 查询成功响应
 * @returns {number} 200.code - 状态码 200
 * @returns {object} 200.data - PIN状态数据
 * @returns {boolean} 200.data.has_pin - 是否已设置PIN码
 * @returns {boolean} 200.data.is_locked - 是否处于锁定状态
 * @returns {number} 200.data.remaining_minutes - 锁定剩余时间（分钟）
 * @returns {number} 200.data.attempts - 当前错误尝试次数
 * 
 * @returns {object} 404 - 用户不存在
 * @returns {object} 500 - 服务器错误
 * 
 * @example
 * // 请求示例
 * GET /api/auth/pin/status
 * Authorization: Bearer <token>
 * 
 * // 响应示例（已设置PIN，未锁定）
 * {
 *   "code": 200,
 *   "data": {
 *     "has_pin": true,
 *     "is_locked": false,
 *     "remaining_minutes": 0,
 *     "attempts": 0
 *   }
 * }
 * 
 * // 响应示例（已锁定）
 * {
 *   "code": 200,
 *   "data": {
 *     "has_pin": true,
 *     "is_locked": true,
 *     "remaining_minutes": 25,
 *     "attempts": 3
 *   }
 * }
 * 
 * 使用场景：
 * 1. 前端页面加载时，检查用户是否设置了PIN
 * 2. 敏感操作前，检查PIN是否被锁定
 * 3. 显示锁定状态提示，告知用户剩余时间
 */
router.get('/pin/status', authenticateToken, async (req, res) => {
    try {
        // 1. 从JWT Token获取用户ID
        const userId = req.user.user_id;
        
        // 2. 查询PIN相关信息
        const [users] = await db.query(
            'SELECT pin_hash, pin_attempts, pin_locked_until FROM users WHERE user_id = ?',
            [userId]
        );
        
        // 3. 检查用户是否存在
        if (users.length === 0) {
            return res.status(404).json({ 
                code: 404, 
                message: '用户不存在' 
            });
        }
        
        const user = users[0];
        
        // 4. 初始化锁定状态变量
        let isLocked = false;           // 是否锁定
        let remainingMinutes = 0;       // 剩余锁定时间（分钟）
        
        // 5. 检查锁定状态
        if (user.pin_locked_until) {
            // 将数据库时间转为Date对象
            const lockedUntil = new Date(user.pin_locked_until);
            const now = new Date();
            
            // 如果当前时间还未到锁定到期时间，说明仍在锁定中
            if (now < lockedUntil) {
                isLocked = true;
                // 计算剩余时间（向上取整）
                remainingMinutes = Math.ceil((lockedUntil - now) / 60000);
            }
            // 如果锁定已过期，不需要特殊处理
            // 下次验证时会自动重置
        }
        
        // 6. 返回PIN状态信息
        res.json({
            code: 200,
            data: {
                has_pin: !!user.pin_hash,      // !!转换为布尔值：有pin_hash为true，无为false
                is_locked: isLocked,           // 是否锁定
                remaining_minutes: remainingMinutes,  // 剩余锁定时间
                attempts: user.pin_attempts    // 当前错误次数
            }
        });
        
    } catch (error) {
        // 7. 错误处理
        console.error('查询PIN状态错误:', error);
        res.status(500).json({ 
            code: 500, 
            message: '查询失败', 
            error: error.message 
        });
    }
});

// ==================== 导出路由 ====================

/**
 * 导出认证路由模块
 * 
 * @exports router - Express路由实例
 * 
 * 包含的路由：
 * - POST /register - 用户注册
 * - POST /login - 用户登录
 * - POST /pin/set - 设置PIN码
 * - POST /pin/verify - 验证PIN码
 * - GET /pin/status - 查询PIN状态
 */
module.exports = router; 